Skip to content

[Parser] Attributes on MacroExpansionDeclSyntax #1650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 15, 2023

Conversation

rintaro
Copy link
Member

@rintaro rintaro commented May 10, 2023

  • Add attributes and modifiers to MacroExpansionDeclSyntax
  • Diagnose whitespaces between # and the macro name
  • Attach attributes to MacroExpansionDeclSyntax

rdar://107386648

let (unexpectedBeforePound, poundKeyword) = self.eat(handle)
// Don't allow space between '#' and the macro name.
if poundKeyword.trailingTriviaByteLength != 0 || self.currentToken.leadingTriviaByteLength != 0 {
return RawMacroExpansionDeclSyntax(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent here is that parse # followed by whitespaces as #<missing identifier>, and let the caller do the recovery.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still continue macro attribute parsing if the identifier is at the same line as the #. I would expect that to yield better recovery.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would you expect as the parsed tree. How do we embed "hasError"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example if you have # elif, we should have unexpectedBeforeMacroName = ' elif' and macroName should be a missing identifier with text elif. That’s also what we do when parsing a. b (where whitespace around the . is not balanced).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Will do.

let poundKeyword = self.consumeAnyToken()
let (unexpectedBeforeMacro, macro) = self.expectIdentifier()
let (unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. #class is invalid but should be recoverable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test case for #<keyword>?

@rintaro
Copy link
Member Author

rintaro commented May 10, 2023

@swift-ci Please test

@rintaro rintaro force-pushed the macros-attributes-rdar107386648 branch from 0b2562a to 72bc06a Compare May 11, 2023 19:02
@rintaro
Copy link
Member Author

rintaro commented May 11, 2023

@swift-ci Please test

@rintaro rintaro marked this pull request as ready for review May 11, 2023 19:08
@rintaro rintaro requested a review from ahoppen as a code owner May 11, 2023 19:08
@rintaro rintaro requested a review from bnbarham May 11, 2023 21:30
],
fixedSource: """
#if MY_FLAG
#<#identifier#>elif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lost the space here 🤔?

Copy link
Member Author

@rintaro rintaro May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.. no idea what happened.. I guess the diagnostic is after the trailing trivia, but the diagnostic system removed the trailing trivia of # because that's preferable. @ahoppen any idea?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To start, this gets parsed as follows

SourceFileSyntax
IfConfigDeclSyntax
├─clauses: IfConfigClauseListSyntax
│ ╰─[0]: IfConfigClauseSyntax
│   ├─poundKeyword: poundIfKeyword
│   ├─condition: IdentifierExprSyntax
│   │ ╰─identifier: identifier("MY_FLAG")
│   ╰─elements: CodeBlockItemListSyntax
│     ├─[0]: CodeBlockItemSyntax
│     │ ├─item: MacroExpansionExprSyntax
│     │ │ ├─poundToken: pound
│     │ │ ├─macro: identifier("") MISSING
│     │ │ ╰─argumentList: TupleExprElementListSyntax
│     │ ╰─semicolon: semicolon MISSING
│     ╰─[1]: CodeBlockItemSyntax
│       ╰─item: IdentifierExprSyntax
│         ╰─identifier: identifier("elif")
╰─poundEndif: poundEndifKeyword

When inserting the missing identifier, we see that # and identifiers don’t need to be separated by a space, so we remove the space. This is really useful for example if you have someCall(1 2) where a comma is missing. In this case you want to insert it after immediate after the 1 and not the space.

So now to the next question: Why don’t we add a space after <#identifier#>. For this, we look at the fixed up view of the tree and discover that the missing identifier is followed by a semicolon in that view. And identifier and semicolons don’t need to be separated by spaces. So no space that needs to be inserted here. We just suppress the missing semicolon diagnostic because the code item already has an error. Because most of the time the missing semicolon diagnostic is actually really misleading.

I think fix here would be to improve how we recover here in general so that elif doesn’t end up in a separate CodeBlockItem anymore.

@@ -1427,6 +1427,125 @@ final class DeclarationTests: XCTestCase {
)
}

func testAttributedMacroExpansionDeclaration() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could add a test when split by a newline rather than space? And also one for with both attributes and modifiers?

let poundKeyword = self.consumeAnyToken()
let (unexpectedBeforeMacro, macro) = self.expectIdentifier()
let (unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a test case for #<keyword>?

@bnbarham
Copy link
Contributor

@TiagoMaiaL note that this fixes part of #1395 (ie. whitespace between # and the identifier, but not @).

@rintaro rintaro force-pushed the macros-attributes-rdar107386648 branch from 72bc06a to 424d334 Compare May 12, 2023 04:37
@rintaro
Copy link
Member Author

rintaro commented May 12, 2023

@swift-ci Please test

@rintaro
Copy link
Member Author

rintaro commented May 12, 2023

@swift-ci Please test Windows

* Add `attributes` and `modifiers` to `MacroExpansionDeclSyntax`
* Diagnose whitespaces between # and the macro name
* Attach attributes to MacroExpansionDeclSyntax

rdar://107386648
@rintaro rintaro force-pushed the macros-attributes-rdar107386648 branch from 424d334 to fd89876 Compare May 12, 2023 06:36
@rintaro
Copy link
Member Author

rintaro commented May 12, 2023

@swift-ci Please test

1 similar comment
@rintaro
Copy link
Member Author

rintaro commented May 12, 2023

@swift-ci Please test

let (unexpectedBeforePound, poundKeyword) = self.eat(handle)
// Don't allow space between '#' and the macro name.
if poundKeyword.trailingTriviaByteLength != 0 || self.currentToken.leadingTriviaByteLength != 0 {
return RawMacroExpansionDeclSyntax(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still continue macro attribute parsing if the identifier is at the same line as the #. I would expect that to yield better recovery.

],
fixedSource: """
#if MY_FLAG
#<#identifier#>elif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To start, this gets parsed as follows

SourceFileSyntax
IfConfigDeclSyntax
├─clauses: IfConfigClauseListSyntax
│ ╰─[0]: IfConfigClauseSyntax
│   ├─poundKeyword: poundIfKeyword
│   ├─condition: IdentifierExprSyntax
│   │ ╰─identifier: identifier("MY_FLAG")
│   ╰─elements: CodeBlockItemListSyntax
│     ├─[0]: CodeBlockItemSyntax
│     │ ├─item: MacroExpansionExprSyntax
│     │ │ ├─poundToken: pound
│     │ │ ├─macro: identifier("") MISSING
│     │ │ ╰─argumentList: TupleExprElementListSyntax
│     │ ╰─semicolon: semicolon MISSING
│     ╰─[1]: CodeBlockItemSyntax
│       ╰─item: IdentifierExprSyntax
│         ╰─identifier: identifier("elif")
╰─poundEndif: poundEndifKeyword

When inserting the missing identifier, we see that # and identifiers don’t need to be separated by a space, so we remove the space. This is really useful for example if you have someCall(1 2) where a comma is missing. In this case you want to insert it after immediate after the 1 and not the space.

So now to the next question: Why don’t we add a space after <#identifier#>. For this, we look at the fixed up view of the tree and discover that the missing identifier is followed by a semicolon in that view. And identifier and semicolons don’t need to be separated by spaces. So no space that needs to be inserted here. We just suppress the missing semicolon diagnostic because the code item already has an error. Because most of the time the missing semicolon diagnostic is actually really misleading.

I think fix here would be to improve how we recover here in general so that elif doesn’t end up in a separate CodeBlockItem anymore.

Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rintaro and I chatted offline that he will address the comments in a follow-up PR

@rintaro rintaro merged commit 4cd9e58 into swiftlang:main May 15, 2023
ahoppen added a commit to ahoppen/swift-syntax that referenced this pull request May 15, 2023
This addresses the review comments I made on swiftlang#1650.

Co-Authored-By: Rintaro Ishizaki <[email protected]>
@ahoppen
Copy link
Member

ahoppen commented May 15, 2023

Addressing the review comments in #1665.

ahoppen added a commit to ahoppen/swift-syntax that referenced this pull request May 15, 2023
This addresses the review comments I made on swiftlang#1650.

Co-Authored-By: Rintaro Ishizaki <[email protected]>
ahoppen added a commit to ahoppen/swift-syntax that referenced this pull request May 25, 2023
This addresses the review comments I made on swiftlang#1650.

Co-Authored-By: Rintaro Ishizaki <[email protected]>
ahoppen added a commit to ahoppen/swift-syntax that referenced this pull request Jun 1, 2023
This addresses the review comments I made on swiftlang#1650.

Co-Authored-By: Rintaro Ishizaki <[email protected]>
ahoppen added a commit to ahoppen/swift-syntax that referenced this pull request Jun 2, 2023
This addresses the review comments I made on swiftlang#1650.

Co-Authored-By: Rintaro Ishizaki <[email protected]>
ahoppen added a commit to ahoppen/swift-syntax that referenced this pull request Jun 2, 2023
This addresses the review comments I made on swiftlang#1650.

Co-Authored-By: Rintaro Ishizaki <[email protected]>
ahoppen added a commit that referenced this pull request Jun 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants